﻿using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using Troschuetz.Random;

namespace Kindohm.Life.Library
{
    public class Creature : TerminalLifeForm
    {
        int sight;
        protected int hunger;
        int health;
        int hungerHuntThreshold;
        protected int hungerStarvationThreshold;
        int matingThreshold;
        int libido;
        Sex sex;
        int children;
        int generation;
        int maxHealth;
        protected Creature plannedMate;

        public static Creature Create()
        {
            Creature creature = new Creature();
            Creature.Initialize(creature, true);
            return creature;
        }

        protected static void Initialize(Creature creature, bool includeRandomValues)
        {
            creature.Id = Guid.NewGuid();
            creature.hunger = 0;
            creature.Name = WorldIdentity.GetRandomName() + " " + WorldIdentity.GetRandomName();
            creature.Position = new Point(-1, -1);

            if (includeRandomValues)
            {
                creature.health = NormalGenerator.Next(200, 30);
                creature.maxHealth = creature.health;
                creature.sight = NormalGenerator.Next(10, 5);
                creature.MaxHeartbeats = NormalGenerator.Next(2000, 300);
                creature.hungerHuntThreshold = NormalGenerator.Next(300, 50);
                creature.hungerStarvationThreshold = NormalGenerator.Next(600, 50);
                creature.matingThreshold = NormalGenerator.Next(300, 50);
                creature.libido = Randomizer.Next(1,creature.matingThreshold);
                creature.hunger = Randomizer.Next(1,creature.hungerHuntThreshold);
                creature.GrowInterval = NormalGenerator.Next(300, 50);

                int next = NormalGenerator.Next(1000, 100);
                if (next >= 1000)
                {
                    creature.sex = Sex.Male;
                }
                else
                {
                    creature.sex = Sex.Female;
                }
            }
        }

        #region properties    
   
        public Creature PlannedMate
        {
            get { return this.plannedMate; }
        }

        public int MaxHealth
        {
            get { return this.maxHealth; }
            set { this.maxHealth = value; }
        }

        public Sex Sex
        {
            get { return this.sex; }
            set { this.sex = value; }
        }

        public bool IsHorny
        {
            get { return this.libido >= this.matingThreshold; }
        }

        public bool IsHungry
        {
            get { return this.hunger >= this.hungerHuntThreshold; }
        }

        public int Libido
        {
            get { return this.libido; }
            set { this.libido = value; }
        }      

        public int Children
        {
            get { return this.children; }
        }

        public int MatingThreshold
        {
            get { return this.matingThreshold; }
            set { this.matingThreshold = value; }
        }
     
        public int Health
        {
            get { return this.health; }
            set { this.health = value; }
        }

        public int Hunger
        {
            get { return this.hunger; }
            set { this.hunger = value; }
        }

        public int Sight
        {
            get { return this.sight; }
            set { this.sight = value; }
        }

        public int HungerHuntThreshold
        {
            get { return this.hungerHuntThreshold; }
            set { this.hungerHuntThreshold = value; }
        }

        public int HungerStarvationThreshold
        {
            get { return this.hungerStarvationThreshold; }
            set { this.hungerStarvationThreshold = value; }
        }

        public int Generation
        {
            get { return this.generation; }
            set { this.generation = value; }
        }

        #endregion

        public virtual void Mutate()
        {
            System.Threading.Thread.Sleep(1);
            string property;
            int next = Randomizer.Next(1, 100);

            if (next > 80){
                property = "sight";
            }
            else if (next > 60)
            {
                property = "hungerhuntthreshold";
            }
            else if (next > 40)
            {
                property = "hungerstarvationthreshold";
            }
            else if (next > 20)
            {
                property = "matingthreshold";
            }
            else 
            {
                property = "growinterval";
            }

            System.Threading.Thread.Sleep(1);
            int newValue = 0;
            int oldValue = 0;
            double factor = NormalGenerator.NextDouble(1.0, .5);

            switch (property)
            {
                case "sight":
                    {
                        oldValue = this.sight;
                        this.sight = Convert.ToInt32(factor * Convert.ToDouble(this.sight));
                        newValue = this.sight;
                        break;
                    }
                case "maxhealth":
                    {
                        oldValue = this.maxHealth;
                        this.maxHealth = Convert.ToInt32(factor * Convert.ToDouble(this.maxHealth));
                        newValue = this.maxHealth;
                        break;
                    }
                case "maxheartbeats":
                    {
                        oldValue = this.MaxHeartbeats;
                        this.MaxHeartbeats = Convert.ToInt32(factor * Convert.ToDouble(this.MaxHeartbeats));
                        newValue= this.MaxHeartbeats;
                        break;
                    }
                case "hungerhuntthreshold":
                    {
                        oldValue = this.hungerHuntThreshold;
                        this.hungerHuntThreshold = Convert.ToInt32(factor * Convert.ToDouble(this.hungerHuntThreshold));
                        newValue = this.hungerHuntThreshold;
                        break;
                    }
                case "hungerstarvationthreshold":
                    {
                        oldValue = this.hungerStarvationThreshold;
                        this.hungerStarvationThreshold = Convert.ToInt32(factor * Convert.ToDouble(this.hungerStarvationThreshold));
                        newValue = this.hungerStarvationThreshold;
                        break;
                    }
                case "matingthreshold":
                    {
                        oldValue = this.matingThreshold;
                        this.matingThreshold = Convert.ToInt32(factor * Convert.ToDouble(this.matingThreshold));
                        newValue = this.matingThreshold;
                        break;
                    }
                case "growinterval":
                    {
                        oldValue = this.GrowInterval;
                        this.GrowInterval = Convert.ToInt32(factor * Convert.ToDouble(this.GrowInterval));
                        newValue = this.GrowInterval;
                        break;
                    }
                default:
                    {
                        throw new MutateException("Unknown property selected to mutate.");
                }
            }

            this.OnMutate(property, oldValue, newValue, factor);


        }

        public static bool MutateOccurs()
        {
            int next = Randomizer.Next(1,100);
            return next > 60;
        }

        protected void OnMutate(string property, int oldValue, int newValue, double factor)
        {
            if (this.Mutating != null)
            {
                MutatingEventArgs e = new MutatingEventArgs(property, factor, oldValue, newValue);
                this.Mutating(this, e);
            }
        }

        public override void RunHeartbeat()
        {
            if (this.Done || this.IsDead)
            {
                return;
            }

            base.RunHeartbeat();
            this.ProcessHunger();
            this.ProcessLibido();
            this.ProcessFreeWill();
        }

        protected virtual void ProcessFreeWill()
        {
        }

        protected void GoForMate(Creature mate)
        {
            if (!mate.IsHorny || mate.IsDead)
            {
                //didn't get there in time.
                this.plannedMate = null;
                return;
            }

            if (Board.AreAdjacent(mate.Position, this.Position))
            {
                this.Mate(mate);
                return;
            }
            else
            {
                this.plannedMate = mate;
                this.WalkTo(mate.Position);
                return;
            }
        }

        protected virtual Creature FindIdealMate()
        {
            if (this.Board == null)
            {
                return null;
            }

            MateFinder finder = new MateFinder();
            Sex targetSex = Sex.Male;
            if (this.Sex == Sex.Male)
            {
                targetSex = Sex.Female;
            }
            return finder.FindIdealMate(this.Board, this.Position, this.sight, targetSex, typeof(Creature));
        }

        void ProcessHunger()
        {
            if (this.Done || this.IsDead)
            {
                return;
            } 
            
            this.hunger++;

            if (this.hunger > this.hungerStarvationThreshold)
            {
                this.health--;
            }

            if (this.health <= 0)
            {
                this.Die(CauseOfDeath.Starvation);
            }
        }

        void ProcessLibido()
        {
            if (this.Done || this.IsDead)
            {
                return;
            }

            this.libido++;
        }

        public override void Grow()
        {
            this.Sight++;
            this.HungerStarvationThreshold++;
            this.MaxHealth++;
        }

        public virtual void Eat(IEdible target)
        {
            int oldHunger = this.hunger;
            int oldHealth = this.health;
            int nutrition = target.Nutrition;

            int newHunger = this.hunger;
            int newHealth = this.health;

            this.hunger = this.hunger - target.Nutrition;
            this.health = this.health + target.Nutrition;

            if (this.hunger < 0)
            {
                this.hunger = 0;
            }

            if (this.health > this.maxHealth)
            {
                this.health = this.maxHealth;
            }


            target.BeEaten();

            this.OnEat(target, oldHealth, newHealth, oldHunger, newHunger, nutrition);
        }

        protected virtual void OnEat(IEdible food, int oldHealth, int newHealth, int oldHunger, int newHunger, int nutrition)
        {
            if (this.Eating != null)
            {
                EatingEventArgs e = new EatingEventArgs(this, food, oldHealth, newHealth, oldHunger, newHunger, nutrition);
                this.Eating(this, e);
            }
        }

        public void Walk()
        {
            Collection<Point> points = this.GetValidDestinationPoints();
            if (points == null)
            {
                return;
            }

            if (points.Count == 0)
            {
                //stuck
                return;
            }

            int next = Randomizer.Next(0, points.Count);
            Point target = points[next];
            this.OnWalk();
            base.LogVerboseMessage(string.Format("Walking to {0}.", target.ToString()));
            this.Move(target);
        }

        protected void OnWalk()
        {
            if (this.Walking != null)
            {
                this.Walking(this, EventArgs.Empty);
            }
        }
        
        public void WalkTo(Point target)
        {
            if (this.Position.Equals(target))
            {
                return;
            }

            int xDif, yDif;
            xDif = target.X - this.Position.X;
            yDif = target.Y - this.Position.Y;

            int x = 0;
            int y = 0;
            if (xDif > 0)
            {
                x = 1;
            }
            else if (xDif < 0)
            {
                x = -1;
            }

            if (yDif > 0)
            {
                y = 1;
            }
            else if (yDif < 0)
            {
                y = -1;
            }

            Point newPoint = new Point(this.Position.X + x, this.Position.Y + y);

            //check to see if target point is occupied.  
            //if so, stay put

            if (this.Board != null)
            {
                if (this.Board.BoardObjects[newPoint] == null)
                {
                    this.Move(newPoint);
                }
            }
        }

        protected override void ProduceRemains(Point point)
        {
            if (this.Board != null)
            {
                Corpse corpse = Corpse.Create();
                corpse.Nutrition = this.Health;
                this.Board.AddObject(corpse, point);
                if (this.World != null)
                {
                    this.World.RegisterNewObject(corpse);
                }
                this.OnProduceRemains(corpse);
            }
        }

        protected void OnProduceRemains(Corpse corpse)
        {
            if (this.ProducedRemains != null)
            {
                ProducedRemainsEventArgs e = new ProducedRemainsEventArgs(corpse);
                this.ProducedRemains(this, e);
            }
        }

        public event EventHandler<ProducedRemainsEventArgs> ProducedRemains;
        public event EventHandler<EatingEventArgs> Eating;
        public event EventHandler<EventArgs> Walking;
        public event EventHandler<MateEventArgs> Mating;
        public event EventHandler<MutatingEventArgs> Mutating;

        public override string ToString()
        {
            return this.Name + " [" + this.health.ToString() + "]";
        }

        public override void BeEaten()
        {
        }

        public virtual Creature Mate(Creature mate)
        {
            if (this.Sex == mate.Sex)
            {
                throw new MatingException("Sorry, homosexuality isn't supported yet.");
            }

            System.Reflection.MethodInfo method = mate.GetType().GetMethod("Create");
            Creature offspring = method.Invoke(null, null) as Creature;
            Creature.Initialize(offspring, false);

            offspring.MaxHealth = Creature.CalculateMean(this.MaxHealth, mate.MaxHealth);
            offspring.Health = offspring.MaxHealth;
            offspring.Sight = Creature.CalculateMean(this.Sight, mate.Sight);
            offspring.MaxHeartbeats = Creature.CalculateMean(this.MaxHeartbeats, mate.MaxHeartbeats);
            offspring.hungerHuntThreshold = Creature.CalculateMean(this.hungerHuntThreshold, mate.hungerHuntThreshold);
            offspring.hungerStarvationThreshold = Creature.CalculateMean(this.hungerStarvationThreshold, mate.hungerStarvationThreshold);
            offspring.matingThreshold = Creature.CalculateMean(this.matingThreshold, mate.matingThreshold);
            offspring.GrowInterval = Creature.CalculateMean(this.GrowInterval, mate.GrowInterval);

            int generation = this.generation + 1;

            if (mate.generation > this.generation)
            {
                generation = mate.generation + 1;
            }
            offspring.generation = generation;

            int next = NormalGenerator.Next(1000, 100);
            if (next >= 1000)
            {
                offspring.sex = Sex.Male;
            }
            else
            {
                offspring.sex = Sex.Female;
            }

            this.RegisterOffspring(offspring);

            Creature father, mother;
            if (this.sex == Sex.Female)
            {
                mother = this;
                father = mate;
            }
            else
            {
                mother = mate;
                father = this;
            }

            this.children++;
            mate.children++;
            this.plannedMate = null;
            mate.plannedMate = null;
            this.libido = 0;
            mate.libido = 0;

            this.OnMate(mate, offspring);

            bool mutate = Creature.MutateOccurs();
            if (mutate)
            {
                offspring.Mutate();
            }


            return offspring;
        }

        protected void OnMate(Creature mate, Creature offspring)
        {
            if (this.Mating != null)
            {
                MateEventArgs e = new MateEventArgs(this, mate, offspring);
                this.Mating(this, e);
            }
        }

        void RegisterOffspring(Creature offspring)
        {
            bool couldNotAddToBoard = false;

            if (this.Board != null)
            {
                Collection<Point> points = this.Board.GetEmptyPositionsWithinRange(this.Position, 10);
                if (points.Count == 0)
                {
                    couldNotAddToBoard = true;
                }
                else
                {
                    this.Board.AddObject(offspring, points[0]);
                }
            }

            if (this.World != null && couldNotAddToBoard == false)
            {
                this.World.RegisterNewObject(offspring);
            }
        }

        static int CalculateMean(int value1, int value2)
        {
            return (value1 + value2) / 2;
        }

    }
}
